unlink("banner.png")
file.copy("../images/banner1.png", "banner.png")
Source code: https://github.com/markziemann/cryptoblog/blob/main/alerts/alerts.Rmd
In the backtesting analysis, I have established different profitable strategies for timing entry/exits. Click on the hyperlinks below to see how these signals were identified.
This script will examine the price action and these trade signals just before the daily close. If the signal is TRUE, it is bullish and if FALSE it is bearish. When there is a switch from bearish to bullish it is a buy signal and when it switches from bullish to bearish it is a sell signal. This HTML is updated just before the daily close each day, but push notifications for any trade signals are also sent by push bullet.
To receive these signals in real-time, subscribe to the following channels - it’s free for a limited time!
https://www.pushbullet.com/channel?tag=btc_signal
https://www.pushbullet.com/channel?tag=eth_signal
If you need some help to design your own trading signals/strategies, I am happy to help for a fee. email me at mark.ziemann{αt}gmail.com for any enquiries/suggestions/feedback.
This report is distributed for FREE under the MIT licence, but if you find it useful, consider a small tip.
XMR:4BGrdeAF5qyJQXjzWF4W5uCZF7WuwJU16BfPtgg1WJMnf33jZMtLvoF1jRtZBGpLtz5BQZaLYiBFJJC488anty64FB7SASD
library(png)
library(grid)
img <- readPNG("../images/donations.png")
grid.raster(img)
Reminder: this analysis is not financial advice.
suppressPackageStartupMessages({
library("jsonlite")
library("tidyverse")
library("runner")
library("quantmod")
library("TTR")
library("RPushbullet")
library("kableExtra")
})
TESTING = FALSE
system("DATE=$(date -u) ; sed \"s/MYTIME/${DATE}/\" ../index0.html > ../index1.html")
mydate <- Sys.time()
attr(mydate, "tzone") <- "UTC"
mydate <- as.Date(mydate)
Obtaining BTC price data (daily) for the last 300 days.
URL="https://web-api.coinmarketcap.com/v1/cryptocurrency/ohlcv/historical?symbol=BTC&convert=USD&interval=daily&count=300"
download.file(URL,destfile="btcdat.txt")
btcdat <- fromJSON("btcdat.txt")
price <- btcdat$data$quotes
price <- data.frame( as.Date(price$time_close) , price$quote$USD$high, price$quote$USD$low, price$quote$USD$close)
colnames(price) <- c("date","high","low","close")
tail(price) %>% kbl() %>% kable_paper("hover", full_width = F)
| date | high | low | close | |
|---|---|---|---|---|
| 294 | 2022-06-28 | 21164.42 | 20228.81 | 20280.64 |
| 295 | 2022-06-29 | 20364.16 | 19937.79 | 20104.02 |
| 296 | 2022-06-30 | 20141.16 | 18729.66 | 19784.73 |
| 297 | 2022-07-01 | 20632.67 | 19073.71 | 19269.37 |
| 298 | 2022-07-02 | 19371.75 | 19027.08 | 19242.26 |
| 299 | 2022-07-03 | 19558.27 | 18966.95 | 19297.08 |
Now gather hourly price data for the last 23 hours.
URL="https://web-api.coinmarketcap.com/v1/cryptocurrency/ohlcv/historical?symbol=BTC&convert=USD&interval=hourly&time_period=hourly&count=11"
download.file(URL,destfile="btcdat.txt")
btcdat <- fromJSON("btcdat.txt")
price2 <- btcdat$data$quotes
price2 <- data.frame( as.Date(price2$time_close) , price2$quote$USD$high, price2$quote$USD$low, price2$quote$USD$close,stringsAsFactors=FALSE)
colnames(price2) <- c("date","high","low","close")
tail(price2) %>% kbl() %>% kable_paper("hover", full_width = F)
| date | high | low | close | |
|---|---|---|---|---|
| 5 | 2022-07-04 | 19154.09 | 19108.33 | 19139.08 |
| 6 | 2022-07-04 | 19389.75 | 19136.71 | 19377.49 |
| 7 | 2022-07-04 | 19483.31 | 19367.68 | 19461.81 |
| 8 | 2022-07-04 | 19762.37 | 19460.99 | 19762.37 |
| 9 | 2022-07-04 | 19816.99 | 19673.57 | 19701.84 |
| 10 | 2022-07-04 | 19725.75 | 19550.29 | 19550.29 |
high <- max(price2[,2])
low <- min(price2[,3])
close <- price2[nrow(price2),4]
df <- data.frame(date=mydate,high=high,low=low,close=close,stringsAsFactors=FALSE)
price <- rbind(price,df)
tail(price) %>% kbl() %>% kable_paper("hover", full_width = F)
| date | high | low | close | |
|---|---|---|---|---|
| 295 | 2022-06-29 | 20364.16 | 19937.79 | 20104.02 |
| 296 | 2022-06-30 | 20141.16 | 18729.66 | 19784.73 |
| 297 | 2022-07-01 | 20632.67 | 19073.71 | 19269.37 |
| 298 | 2022-07-02 | 19371.75 | 19027.08 | 19242.26 |
| 299 | 2022-07-03 | 19558.27 | 18966.95 | 19297.08 |
| 300 | 2022-07-04 | 19816.99 | 19084.24 | 19550.29 |
if ( TESTING == TRUE) {
price[nrow(price),"close"] <- price[nrow(price),"close"] * 2
}
params <- read.table("https://mdz-analytics.com/altcoins/BTC/BTC_dat.txt", header=TRUE)
params %>% kbl(caption="BTC optimised backtested parameters") %>% kable_paper("hover", full_width = F)
| indicator | parameter | meanROI | totalROI | ntrades | ndays | xhodl |
|---|---|---|---|---|---|---|
| SMA | 43 | 1.163350 | 1875.6348 | 84 | 3263 | 6.758857 |
| EMA | 76 | 1.232865 | 968.4484 | 57 | 3263 | 3.489807 |
| SMAcross | 21,12 | 1.206000 | 2404.9715 | 74 | 3263 | 8.666323 |
| EMAcross | 48,18 | 1.794426 | 1836.8613 | 20 | 3263 | 6.619136 |
| DMI | 6 | 1.085680 | 3107.7862 | 171 | 3263 | 11.198919 |
| TSI | 69,59,5 | 1.278166 | 1798.8456 | 51 | 3263 | 6.482146 |
| stoch | 18,75,3 | 1.100545 | 2068.5788 | 111 | 3263 | 7.454131 |
| RSI2 | 69,74 | 1.149731 | 1806.6849 | 96 | 3263 | 6.510396 |
Now to determine whether BTC has crossed the SMA line.
price2 <- price
n <- as.numeric(params[params$indicator == "SMA",2])
price2$ma <- SMA(Cl(price2),n=n)
price2$signal <- price2$close > price2$ma
tail(price2) %>% kbl() %>% kable_paper("hover", full_width = F)
| date | high | low | close | ma | signal | |
|---|---|---|---|---|---|---|
| 295 | 2022-06-29 | 20364.16 | 19937.79 | 20104.02 | 26230.63 | FALSE |
| 296 | 2022-06-30 | 20141.16 | 18729.66 | 19784.73 | 26022.82 | FALSE |
| 297 | 2022-07-01 | 20632.67 | 19073.71 | 19269.37 | 25765.96 | FALSE |
| 298 | 2022-07-02 | 19371.75 | 19027.08 | 19242.26 | 25534.37 | FALSE |
| 299 | 2022-07-03 | 19558.27 | 18966.95 | 19297.08 | 25298.67 | FALSE |
| 300 | 2022-07-04 | 19816.99 | 19084.24 | 19550.29 | 25048.12 | FALSE |
today_signal <- price2[nrow(price2),"signal"]
previous_signal <- price2[(nrow(price2)-1),"signal"]
if (today_signal != previous_signal ) {
if ( (today_signal == TRUE ) & (previous_signal == FALSE ) ) {
btc_signal="BUY"
}
if ( (today_signal == FALSE ) & (previous_signal == TRUE ) ) {
btc_signal="SELL"
}
} else {
btc_signal <- "NONE"
}
if (today_signal==TRUE) {
system("sed -i 's/BTC_SMA_DAILY/BULLISH/' ../index1.html")
system("sed -i 's/BTC_SMA_COL/green/' ../index1.html")
} else {
system("sed -i 's/BTC_SMA_DAILY/BEARISH/' ../index1.html")
system("sed -i 's/BTC_SMA_COL/red/' ../index1.html")
}
message(paste("BTC SMA signal:",btc_signal))
## BTC SMA signal: NONE
plot(price2$close~as.Date(price2$date),type="l",
xlab="Date",ylab="price (USD)",main="BTC SMA")
grid()
lines(price2$ma~ as.Date(price2$date) ,col="red")
Send a push notification.
MYNOTE = NULL
if ( btc_signal != "NONE") {
MYNOTE <- paste("The BTC SMA signal is", btc_signal)
}
The SMA cross is a highly profitable strategy.
price2 <- price
n <- as.numeric(unlist(strsplit(params[params$indicator == "SMAcross",2],",")))
n1 <- n[1]
n2 <- n[2]
price2$ma1 <- SMA(Cl(price2),n=n1)
price2$ma2 <- SMA(Cl(price2),n=n2)
price2$signal <- price2$ma2 > price2$ma1
tail(price2) %>% kbl() %>% kable_paper("hover", full_width = F)
| date | high | low | close | ma1 | ma2 | signal | |
|---|---|---|---|---|---|---|---|
| 295 | 2022-06-29 | 20364.16 | 19937.79 | 20104.02 | 22346.42 | 20569.62 | FALSE |
| 296 | 2022-06-30 | 20141.16 | 18729.66 | 19784.73 | 21854.64 | 20633.54 | FALSE |
| 297 | 2022-07-01 | 20632.67 | 19073.71 | 19269.37 | 21387.29 | 20526.55 | FALSE |
| 298 | 2022-07-02 | 19371.75 | 19027.08 | 19242.26 | 20953.07 | 20413.44 | FALSE |
| 299 | 2022-07-03 | 19558.27 | 18966.95 | 19297.08 | 20597.57 | 20295.65 | FALSE |
| 300 | 2022-07-04 | 19816.99 | 19084.24 | 19550.29 | 20457.71 | 20259.25 | FALSE |
today_signal <- price2[nrow(price2),"signal"]
previous_signal <- price2[(nrow(price2)-1),"signal"]
if (today_signal != previous_signal ) {
if ( (today_signal == TRUE ) & (previous_signal == FALSE ) ) {
btc_signal="BUY"
}
if ( (today_signal == FALSE ) & (previous_signal == TRUE ) ) {
btc_signal="SELL"
}
} else {
btc_signal <- "NONE"
}
if (today_signal==TRUE) {
system("sed -i 's/BTC_SMA_CROSS_DAILY/BULLISH/' ../index1.html")
system("sed -i 's/BTC_SMA_CROSS_COL/green/' ../index1.html")
} else {
system("sed -i 's/BTC_SMA_CROSS_DAILY/BEARISH/' ../index1.html")
system("sed -i 's/BTC_SMA_CROSS_COL/red/' ../index1.html")
}
message(paste("BTC SMA cross signal is",btc_signal))
## BTC SMA cross signal is NONE
plot(price2$close~as.Date(price2$date),type="l",
xlab="Date",ylab="price (USD)",main="BTC w 12/21 day SMA cross")
grid()
lines(price2$ma1~ as.Date(price2$date) ,col="red")
lines(price2$ma2~ as.Date(price2$date) ,col="blue")
Send a push notification.
if ( btc_signal != "NONE") {
MYNOTE <- paste(MYNOTE, "", btc_signal)
}
Directional movement indicator is a good approach to identify trend changes.
n <- as.numeric(params[params$indicator == "DMI",2])
dmi.adx <- ADX(price[,c("high","low","close")],n=n)
price2 <- cbind(price,dmi.adx)
price2$signal <- dmi.adx[,1] > dmi.adx[,2]
tail(price2) %>% kbl() %>% kable_paper("hover", full_width = F)
| date | high | low | close | DIp | DIn | DX | ADX | signal | |
|---|---|---|---|---|---|---|---|---|---|
| 295 | 2022-06-29 | 20364.16 | 19937.79 | 20104.02 | 11.202759 | 31.92161 | 48.04442 | 45.61667 | FALSE |
| 296 | 2022-06-30 | 20141.16 | 18729.66 | 19784.73 | 8.766389 | 43.59382 | 66.51507 | 49.09974 | FALSE |
| 297 | 2022-07-01 | 20632.67 | 19073.71 | 19269.37 | 13.859239 | 33.83985 | 41.88887 | 47.89793 | FALSE |
| 298 | 2022-07-02 | 19371.75 | 19027.08 | 19242.26 | 13.082641 | 32.70169 | 42.85101 | 47.05677 | FALSE |
| 299 | 2022-07-03 | 19558.27 | 18966.95 | 19297.08 | 14.992019 | 29.31937 | 32.33334 | 44.60287 | FALSE |
| 300 | 2022-07-04 | 19816.99 | 19084.24 | 19550.29 | 17.700132 | 25.41112 | 17.88626 | 40.15010 | FALSE |
today_signal <- price2[nrow(price2),"signal"]
previous_signal <- price2[(nrow(price2)-1),"signal"]
if (today_signal != previous_signal ) {
if ( (today_signal == TRUE ) & (previous_signal == FALSE ) ) {
btc_signal="BUY"
}
if ( (today_signal == FALSE ) & (previous_signal == TRUE ) ) {
btc_signal="SELL"
}
} else {
btc_signal <- "NONE"
}
if (today_signal==TRUE) {
system("sed -i 's/BTC_DMI_DAILY/BULLISH/' ../index1.html")
system("sed -i 's/BTC_DMI_COL/green/' ../index1.html")
} else {
system("sed -i 's/BTC_DMI_DAILY/BEARISH/' ../index1.html")
system("sed -i 's/BTC_DMI_COL/red/' ../index1.html")
}
message(paste("BTC DMI signal:",btc_signal))
## BTC DMI signal: NONE
price2 <- as.data.frame(price2)
par(mfrow=c(2,1))
plot(price2$close~as.Date(price2$date),type="l",
xlab="Date",ylab="price (USD)",main="BTC USD price")
grid()
plot(price2$DIp~as.Date(price2$date),type="l", col="blue",
xlab="Date",ylab="price (USD)",main="BTC 32d DMI")
grid()
lines(price2$DIn ~ as.Date(price2$date) ,col="red")
Send a push notification.
if ( btc_signal != "NONE") {
MYNOTE <- paste(MYNOTE, "BTC DMI is flashing a", btc_signal)
}
Probably the best performing indicator across most price series data.
n <- as.numeric(unlist(strsplit(params[params$indicator == "TSI",2],",")))
n1 <- n[1]
n2 <- n[2]
ns <- n[3]
TSI <- function(x, n.first = 25, n.second = 13, n.signal = 7) {
#True Strength Indicator
#https://school.stockcharts.com/doku.php?id=technical_indicators:true_strength_index
x <- try.xts(x, error = as.matrix)
pc <- x - lag.xts(x, na.pad = T) #force lag.xts to get na padding
dspc <- EMA(EMA(pc, n = n.first), n = n.second)
dsapc <- EMA(EMA(abs(pc), n = n.first), n = n.second)
tsi <- 100 * (dspc/dsapc)
signal <- EMA(tsi, n = n.signal)
r <- cbind(tsi, signal)
r <- reclass(r, x)
if (!is.null(dim(r))) colnames(r) <- c("tsi", "signal")
return(r)
}
tsi <- TSI(price$close , n.first = n1, n.second = n2, n.signal = ns )
colnames(tsi) <- c("tsi","sig")
price2 <- cbind(price,tsi)
price2$signal <- tsi[,1] > tsi[,2]
tail(price2) %>% kbl() %>% kable_paper("hover", full_width = F)
| date | high | low | close | tsi | sig | signal | |
|---|---|---|---|---|---|---|---|
| 295 | 2022-06-29 | 20364.16 | 19937.79 | 20104.02 | -21.59234 | -21.09054 | FALSE |
| 296 | 2022-06-30 | 20141.16 | 18729.66 | 19784.73 | -21.85432 | -21.34513 | FALSE |
| 297 | 2022-07-01 | 20632.67 | 19073.71 | 19269.37 | -22.14294 | -21.61107 | FALSE |
| 298 | 2022-07-02 | 19371.75 | 19027.08 | 19242.26 | -22.41879 | -21.88031 | FALSE |
| 299 | 2022-07-03 | 19558.27 | 18966.95 | 19297.08 | -22.67393 | -22.14485 | FALSE |
| 300 | 2022-07-04 | 19816.99 | 19084.24 | 19550.29 | -22.88476 | -22.39149 | FALSE |
today_signal <- price2[nrow(price2),"signal"]
previous_signal <- price2[(nrow(price2)-1),"signal"]
if (today_signal != previous_signal ) {
if ( (today_signal == TRUE ) & (previous_signal == FALSE ) ) {
btc_signal="BUY"
}
if ( (today_signal == FALSE ) & (previous_signal == TRUE ) ) {
btc_signal="SELL"
}
} else {
btc_signal <- "NONE"
}
if (today_signal==TRUE) {
system("sed -i 's/BTC_TSI_DAILY/BULLISH/' ../index1.html")
system("sed -i 's/BTC_TSI_COL/green/' ../index1.html")
} else {
system("sed -i 's/BTC_TSI_DAILY/BEARISH/' ../index1.html")
system("sed -i 's/BTC_TSI_COL/red/' ../index1.html")
}
message(paste("BTC TSI signal:",btc_signal))
## BTC TSI signal: NONE
par(mfrow=c(2,1))
plot(price2$close ~ as.Date(price2$date),type="l",log="y",
xlab="Date",ylab="price (USD)",main="USD price")
grid()
plot(tsi[,1] ~ as.Date(price2$date),type="l",col="blue",
xlab="Date",ylab="TSI",main="TSI")
lines(as.Date(price2$date), tsi[,2] , col="red" )
grid()
par(mfrow=c(1,1))
Send a push notification.
if ( btc_signal != "NONE") {
MYNOTE <- paste(MYNOTE, "BTC TSI is flashing a", btc_signal)
}
n <- as.numeric(unlist(strsplit(params[params$indicator == "stoch",2],",")))
n1 <- n[1]
n2 <- n[2]
n3 <- n[3]
sto <- stoch(HLC(price), nFastK=n1 , nFastD=n2 , nSlowD=n2 , bounded = TRUE, smooth=n3)
price2 <- cbind(price,sto)
price2$signal <- price2$fastK > price2$fastD
tail(price2) %>% kbl() %>% kable_paper("hover", full_width = F)
| date | high | low | close | fastK | fastD | slowD | signal | |
|---|---|---|---|---|---|---|---|---|
| 295 | 2022-06-29 | 20364.16 | 19937.79 | 20104.02 | 0.2282476 | 0.2538299 | 0.3847318 | FALSE |
| 296 | 2022-06-30 | 20141.16 | 18729.66 | 19784.73 | 0.2230773 | 0.2553514 | 0.3806730 | FALSE |
| 297 | 2022-07-01 | 20632.67 | 19073.71 | 19269.37 | 0.2394570 | 0.2571743 | 0.3767063 | FALSE |
| 298 | 2022-07-02 | 19371.75 | 19027.08 | 19242.26 | 0.2643719 | 0.2588220 | 0.3728196 | TRUE |
| 299 | 2022-07-03 | 19558.27 | 18966.95 | 19297.08 | 0.2995874 | 0.2600417 | 0.3689883 | TRUE |
| 300 | 2022-07-04 | 19816.99 | 19084.24 | 19550.29 | 0.3448080 | 0.2607117 | 0.3652066 | TRUE |
today_signal <- price2[nrow(price2),"signal"]
previous_signal <- price2[(nrow(price2)-1),"signal"]
if (today_signal != previous_signal ) {
if ( (today_signal == TRUE ) & (previous_signal == FALSE ) ) {
btc_signal="BUY"
}
if ( (today_signal == FALSE ) & (previous_signal == TRUE ) ) {
btc_signal="SELL"
}
} else {
btc_signal <- "NONE"
}
if (today_signal==TRUE) {
system("sed -i 's/BTC_STOCH_DAILY/BULLISH/' ../index1.html")
system("sed -i 's/BTC_STOCH_COL/green/' ../index1.html")
} else {
system("sed -i 's/BTC_STOCH_DAILY/BEARISH/' ../index1.html")
system("sed -i 's/BTC_STOCH_COL/red/' ../index1.html")
}
message(paste("BTC stoch signal:",btc_signal))
## BTC stoch signal: NONE
par(mfrow=c(2,1))
plot(price2$close ~ as.Date(price2$date),type="l",log="y",
xlab="Date",ylab="price (USD)",main="USD price")
grid()
plot(price2$fastK ~ as.Date(price2$date),type="l",col="blue",
xlab="Date",ylab="index",main="stochastic oscillator")
lines(as.Date(price2$date), price2$fastD , col="red" )
grid()
par(mfrow=c(1,1))
Send a push notification.
if ( btc_signal != "NONE") {
MYNOTE <- paste(MYNOTE,"BTC Stoch is flashing a", btc_signal)
}
n <- as.numeric(unlist(strsplit(params[params$indicator == "RSI2",2],",")))
n1 <- n[1]
n2 <- n[2]
rsi1 <- RSI(price$close,n=n1,maType=EMA)
rsi2 <- RSI(price$close,n=n2,maType=EMA)
price2 <- cbind(price,rsi1,rsi2)
price2$signal <- price2$rsi1 > price2$rsi2
tail(price2) %>% kbl() %>% kable_paper("hover", full_width = F)
| date | high | low | close | rsi1 | rsi2 | signal | |
|---|---|---|---|---|---|---|---|
| 295 | 2022-06-29 | 20364.16 | 19937.79 | 20104.02 | 35.07874 | 35.47411 | FALSE |
| 296 | 2022-06-30 | 20141.16 | 18729.66 | 19784.73 | 34.69397 | 35.11488 | FALSE |
| 297 | 2022-07-01 | 20632.67 | 19073.71 | 19269.37 | 34.07300 | 34.53495 | FALSE |
| 298 | 2022-07-02 | 19371.75 | 19027.08 | 19242.26 | 34.04000 | 34.50415 | FALSE |
| 299 | 2022-07-03 | 19558.27 | 18966.95 | 19297.08 | 34.17268 | 34.62527 | FALSE |
| 300 | 2022-07-04 | 19816.99 | 19084.24 | 19550.29 | 34.79634 | 35.19400 | FALSE |
today_signal <- price2[nrow(price2),"signal"]
previous_signal <- price2[(nrow(price2)-1),"signal"]
if (today_signal != previous_signal ) {
if ( (today_signal == TRUE ) & (previous_signal == FALSE ) ) {
btc_signal="BUY"
}
if ( (today_signal == FALSE ) & (previous_signal == TRUE ) ) {
btc_signal="SELL"
}
} else {
btc_signal <- "NONE"
}
if (today_signal==TRUE) {
system("sed -i 's/BTC_RSI_DAILY/BULLISH/' ../index1.html")
system("sed -i 's/BTC_RSI_COL/green/' ../index1.html")
} else {
system("sed -i 's/BTC_RSI_DAILY/BEARISH/' ../index1.html")
system("sed -i 's/BTC_RSI_COL/red/' ../index1.html")
}
message(paste("BTC RSI signal:",btc_signal))
## BTC RSI signal: NONE
par(mfrow=c(2,1))
plot(price2$close ~ as.Date(price2$date),type="l",log="y",
xlab="Date",ylab="price (USD)",main="USD price")
grid()
plot(price2$rsi1 ~ as.Date(price2$date),type="l",col="blue",
xlab="Date",ylab="index",main="double RSI")
lines(as.Date(price2$date), price2$rsi2 , col="red" )
grid()
par(mfrow=c(2,1))
plot(price2$close ~ as.Date(price2$date),type="l",log="y",
xlab="Date",ylab="price (USD)",main="USD price")
grid()
plot(price2$rsi1/price2$rsi2 ~ as.Date(price2$date),type="l",
xlab="Date",ylab="RSI ratio",main="double RSI")
grid()
abline(h=1,lty=2,lwd=2)
par(mfrow=c(1,1))
Send a push notification.
if ( btc_signal != "NONE") {
MYNOTE <- paste(MYNOTE,"BTC RSI2 is flashing a", btc_signal)
}
#if ( !is.null(MYNOTE) ) {
# MYNOTE <- paste(MYNOTE,". Visit https://mdz-analytics.com/crypto/alerts.html for the details")
# pbPost("note", "Crypto Alert", MYNOTE, channel="btc_signal")
#}
Obtaining ETH historical data (daily).
URL="https://web-api.coinmarketcap.com/v1/cryptocurrency/ohlcv/historical?symbol=ETH&convert=USD&interval=daily&count=300"
download.file(URL,destfile="ethdat.txt")
ethdat <- fromJSON("ethdat.txt")
price <- ethdat$data$quotes
price <- data.frame( as.Date(price$time_close) , price$quote$USD$high, price$quote$USD$low, price$quote$USD$close)
colnames(price) <- c("date","high","low","close")
tail(price) %>% kbl() %>% kable_paper("hover", full_width = F)
| date | high | low | close | |
|---|---|---|---|---|
| 294 | 2022-06-28 | 1229.739 | 1141.160 | 1144.579 |
| 295 | 2022-06-29 | 1152.685 | 1092.099 | 1098.944 |
| 296 | 2022-06-30 | 1103.690 | 1009.095 | 1067.299 |
| 297 | 2022-07-01 | 1100.224 | 1040.151 | 1059.767 |
| 298 | 2022-07-02 | 1073.053 | 1033.962 | 1066.513 |
| 299 | 2022-07-03 | 1083.419 | 1044.006 | 1073.767 |
Now gather hourly price data for the last 23 hours.
URL="https://web-api.coinmarketcap.com/v1/cryptocurrency/ohlcv/historical?symbol=ETH&convert=USD&interval=hourly&time_period=hourly&count=11"
download.file(URL,destfile="ethdat.txt")
ethdat <- fromJSON("ethdat.txt")
price2 <- ethdat$data$quotes
price2 <- data.frame( as.Date(price2$time_close) , price2$quote$USD$high, price2$quote$USD$low, price2$quote$USD$close,stringsAsFactors=FALSE)
colnames(price2) <- c("date","high","low","close")
tail(price2) %>% kbl() %>% kable_paper("hover", full_width = F)
| date | high | low | close | |
|---|---|---|---|---|
| 5 | 2022-07-04 | 1053.196 | 1051.030 | 1051.787 |
| 6 | 2022-07-04 | 1066.719 | 1051.363 | 1066.540 |
| 7 | 2022-07-04 | 1074.830 | 1066.526 | 1073.826 |
| 8 | 2022-07-04 | 1094.295 | 1072.431 | 1094.295 |
| 9 | 2022-07-04 | 1106.450 | 1094.238 | 1106.387 |
| 10 | 2022-07-04 | 1106.814 | 1094.569 | 1094.569 |
high <- max(price2[,2])
low <- min(price2[,3])
close <- price2[nrow(price2),4]
df <- data.frame(date=Sys.Date(),high=high,low=low,close=close,stringsAsFactors=FALSE)
price <- rbind(price,df)
tail(price) %>% kbl() %>% kable_paper("hover", full_width = F)
| date | high | low | close | |
|---|---|---|---|---|
| 295 | 2022-06-29 | 1152.685 | 1092.099 | 1098.944 |
| 296 | 2022-06-30 | 1103.690 | 1009.095 | 1067.299 |
| 297 | 2022-07-01 | 1100.224 | 1040.151 | 1059.767 |
| 298 | 2022-07-02 | 1073.053 | 1033.962 | 1066.513 |
| 299 | 2022-07-03 | 1083.419 | 1044.006 | 1073.767 |
| 300 | 2022-07-04 | 1106.814 | 1048.396 | 1094.569 |
if ( TESTING == TRUE) {
price[nrow(price),"Close"] <- price[nrow(price),"Close"] * 2
}
params <- read.table("https://mdz-analytics.com/altcoins/ETH/ETH_dat.txt", header=TRUE)
params %>% kbl(caption="ETH optimised backtested parameters") %>% kable_paper("hover", full_width = F)
| indicator | parameter | meanROI | totalROI | ntrades | ndays | xhodl |
|---|---|---|---|---|---|---|
| SMA | 34 | 1.747644 | 32115.470 | 67 | 2467 | 35.403176 |
| EMA | 17 | 1.163883 | 29146.841 | 130 | 2467 | 32.130644 |
| SMAcross | 27,5 | 1.618852 | 27858.643 | 46 | 2467 | 30.710572 |
| EMAcross | 16,13 | 2.634299 | 31742.967 | 30 | 2467 | 34.992539 |
| DMI | 16 | 1.891854 | 23110.349 | 60 | 2467 | 25.476188 |
| TSI | 101,7,20 | 1.368783 | 10773.280 | 49 | 2467 | 11.876156 |
| stoch | 20,5,36 | 1.237952 | 95850.138 | 75 | 2467 | 105.662452 |
| RSI2 | 56,59 | 1.147941 | 2657.575 | 101 | 2467 | 2.929635 |
Now to determine whether ETH has crossed the SMA line.
price2 <- price
n <- as.numeric(params[params$indicator == "SMA",2])
price2$ma <- SMA(Cl(price2),n=n)
price2$signal <- price2$close > price2$ma
tail(price2) %>% kbl() %>% kable_paper("hover", full_width = F)
| date | high | low | close | ma | signal | |
|---|---|---|---|---|---|---|
| 295 | 2022-06-29 | 1152.685 | 1092.099 | 1098.944 | 1461.294 | FALSE |
| 296 | 2022-06-30 | 1103.690 | 1009.095 | 1067.299 | 1441.953 | FALSE |
| 297 | 2022-07-01 | 1100.224 | 1040.151 | 1059.767 | 1420.404 | FALSE |
| 298 | 2022-07-02 | 1073.053 | 1033.962 | 1066.513 | 1398.477 | FALSE |
| 299 | 2022-07-03 | 1083.419 | 1044.006 | 1073.767 | 1371.340 | FALSE |
| 300 | 2022-07-04 | 1106.814 | 1048.396 | 1094.569 | 1346.405 | FALSE |
today_signal <- price2[nrow(price2),"signal"]
previous_signal <- price2[(nrow(price2)-1),"signal"]
if (today_signal != previous_signal ) {
if ( (today_signal == TRUE ) & (previous_signal == FALSE ) ) {
eth_signal="BUY"
}
if ( (today_signal == FALSE ) & (previous_signal == TRUE ) ) {
eth_signal="SELL"
}
} else {
eth_signal <- "NONE"
}
if (today_signal==TRUE) {
system("sed -i 's/ETH_SMA_DAILY/BULLISH/' ../index1.html")
system("sed -i 's/ETH_SMA_COL/green/' ../index1.html")
} else {
system("sed -i 's/ETH_SMA_DAILY/BEARISH/' ../index1.html")
system("sed -i 's/ETH_SMA_COL/red/' ../index1.html")
}
message(paste("ETH SMA signal:",eth_signal))
## ETH SMA signal: NONE
plot(price2$close~as.Date(price2$date),type="l",
xlab="Date",ylab="price (USD)",main="ETH w 34D MA")
grid()
lines(price2$ma~ as.Date(price2$date) ,col="red")
Send a push notification.
MYNOTE = NULL
if ( eth_signal != "NONE") {
MYNOTE <- paste("The ETH SMA signal is", eth_signal)
}
The SMA cross is a highly profitable strategy.
price2 <- price
n <- as.numeric(unlist(strsplit(params[params$indicator == "SMAcross",2],",")))
n1 <- n[1]
n2 <- n[2]
price2$ma1 <- SMA(Cl(price2),n=n1)
price2$ma2 <- SMA(Cl(price2),n=n2)
price2$signal <- price2$ma2 > price2$ma1
tail(price2) %>% kbl() %>% kable_paper("hover", full_width = F)
| date | high | low | close | ma1 | ma2 | signal | |
|---|---|---|---|---|---|---|---|
| 295 | 2022-06-29 | 1152.685 | 1092.099 | 1098.944 | 1361.413 | 1176.096 | FALSE |
| 296 | 2022-06-30 | 1103.690 | 1009.095 | 1067.299 | 1335.199 | 1140.867 | FALSE |
| 297 | 2022-07-01 | 1100.224 | 1040.151 | 1059.767 | 1307.723 | 1112.854 | FALSE |
| 298 | 2022-07-02 | 1073.053 | 1033.962 | 1066.513 | 1280.364 | 1087.420 | FALSE |
| 299 | 2022-07-03 | 1083.419 | 1044.006 | 1073.767 | 1251.271 | 1073.258 | FALSE |
| 300 | 2022-07-04 | 1106.814 | 1048.396 | 1094.569 | 1224.623 | 1072.383 | FALSE |
today_signal <- price2[nrow(price2),"signal"]
previous_signal <- price2[(nrow(price2)-1),"signal"]
if (today_signal != previous_signal ) {
if ( (today_signal == TRUE ) & (previous_signal == FALSE ) ) {
eth_signal="BUY"
}
if ( (today_signal == FALSE ) & (previous_signal == TRUE ) ) {
eth_signal="SELL"
}
} else {
eth_signal <- "NONE"
}
if (today_signal==TRUE) {
system("sed -i 's/ETH_SMA_CROSS_DAILY/BULLISH/' ../index1.html")
system("sed -i 's/ETH_SMA_CROSS_COL/green/' ../index1.html")
} else {
system("sed -i 's/ETH_SMA_CROSS_DAILY/BEARISH/' ../index1.html")
system("sed -i 's/ETH_SMA_CROSS_COL/red/' ../index1.html")
}
message(paste("ETH SMA cross signal:",eth_signal))
## ETH SMA cross signal: NONE
plot(price2$close~as.Date(price2$date),type="l",
xlab="Date",ylab="price (USD)",main="ETH w 5/27 day SMA cross")
grid()
lines(price2$ma1~ as.Date(price2$date) ,col="red")
lines(price2$ma2~ as.Date(price2$date) ,col="blue")
Send a push notification.
if ( eth_signal != "NONE") {
MYNOTE <- paste(MYNOTE,"The ETH SMA cross signal is", eth_signal)
}
n <- as.numeric(params[params$indicator == "DMI",2])
dmi.adx <- ADX(price[,c("high","low","close")],n=n)
price2 <- cbind(price,dmi.adx)
price2$signal <- dmi.adx[,1] > dmi.adx[,2]
tail(price2) %>% kbl() %>% kable_paper("hover", full_width = F)
| date | high | low | close | DIp | DIn | DX | ADX | signal | |
|---|---|---|---|---|---|---|---|---|---|
| 295 | 2022-06-29 | 1152.685 | 1092.099 | 1098.944 | 13.76190 | 35.62348 | 44.26731 | 47.76770 | FALSE |
| 296 | 2022-06-30 | 1103.690 | 1009.095 | 1067.299 | 13.01210 | 38.46332 | 49.44344 | 47.87244 | FALSE |
| 297 | 2022-07-01 | 1100.224 | 1040.151 | 1059.767 | 12.54896 | 37.09430 | 49.44344 | 47.97063 | FALSE |
| 298 | 2022-07-02 | 1073.053 | 1033.962 | 1066.513 | 12.24642 | 36.58167 | 49.83864 | 48.08738 | FALSE |
| 299 | 2022-07-03 | 1083.419 | 1044.006 | 1073.767 | 12.60164 | 35.65711 | 47.77468 | 48.06783 | FALSE |
| 300 | 2022-07-04 | 1106.814 | 1048.396 | 1094.569 | 13.65619 | 34.28709 | 43.03188 | 47.75309 | FALSE |
today_signal <- price2[nrow(price2),"signal"]
previous_signal <- price2[(nrow(price2)-1),"signal"]
if (today_signal != previous_signal ) {
if ( (today_signal == TRUE ) & (previous_signal == FALSE ) ) {
eth_signal="BUY"
}
if ( (today_signal == FALSE ) & (previous_signal == TRUE ) ) {
eth_signal="SELL"
}
} else {
eth_signal <- "NONE"
}
if (today_signal==TRUE) {
system("sed -i 's/ETH_DMI_DAILY/BULLISH/' ../index1.html")
system("sed -i 's/ETH_DMI_COL/green/' ../index1.html")
} else {
system("sed -i 's/ETH_DMI_DAILY/BEARISH/' ../index1.html")
system("sed -i 's/ETH_DMI_COL/red/' ../index1.html")
}
message(paste("ETH DMI signal:",eth_signal))
## ETH DMI signal: NONE
par(mfrow=c(2,1))
plot(price2$close~as.Date(price2$date),type="l",
xlab="Date",ylab="price (USD)",main="ETH USD price")
grid()
plot(price2$DIp~as.Date(price2$date),type="l", col="blue",
xlab="Date",ylab="price (USD)",main="ETH 16d DMI")
grid()
lines(price2$DIn ~ as.Date(price2$date) ,col="red")
Send a push notification.
if ( eth_signal != "NONE") {
MYNOTE <- paste(MYNOTE, "ETH DMI is flashing a", eth_signal)
}
Best indicator.
n <- as.numeric(unlist(strsplit(params[params$indicator == "TSI",2],",")))
n1 <- n[1]
n2 <- n[2]
ns <- n[3]
tsi <- TSI(price$close , n.first = n1, n.second = n2, n.signal = ns )
colnames(tsi) <- c("tsi","sig")
price2 <- cbind(price,tsi)
price2$signal <- tsi[,1] > tsi[,2]
tail(price2) %>% kbl() %>% kable_paper("hover", full_width = F)
| date | high | low | close | tsi | sig | signal | |
|---|---|---|---|---|---|---|---|
| 295 | 2022-06-29 | 1152.685 | 1092.099 | 1098.944 | -24.95332 | -24.52690 | FALSE |
| 296 | 2022-06-30 | 1103.690 | 1009.095 | 1067.299 | -25.25741 | -24.59647 | FALSE |
| 297 | 2022-07-01 | 1100.224 | 1040.151 | 1059.767 | -25.52169 | -24.68459 | FALSE |
| 298 | 2022-07-02 | 1073.053 | 1033.962 | 1066.513 | -25.66597 | -24.77805 | FALSE |
| 299 | 2022-07-03 | 1083.419 | 1044.006 | 1073.767 | -25.71507 | -24.86729 | FALSE |
| 300 | 2022-07-04 | 1106.814 | 1048.396 | 1094.569 | -25.57796 | -24.93498 | FALSE |
today_signal <- price2[nrow(price2),"signal"]
previous_signal <- price2[(nrow(price2)-1),"signal"]
if (today_signal != previous_signal ) {
if ( (today_signal == TRUE ) & (previous_signal == FALSE ) ) {
eth_signal="BUY"
}
if ( (today_signal == FALSE ) & (previous_signal == TRUE ) ) {
eth_signal="SELL"
}
} else {
eth_signal <- "NONE"
}
if (today_signal==TRUE) {
system("sed -i 's/ETH_TSI_DAILY/BULLISH/' ../index1.html")
system("sed -i 's/ETH_TSI_COL/green/' ../index1.html")
} else {
system("sed -i 's/ETH_TSI_DAILY/BEARISH/' ../index1.html")
system("sed -i 's/ETH_TSI_COL/red/' ../index1.html")
}
message(paste("ETH TSI signal:",eth_signal))
## ETH TSI signal: NONE
par(mfrow=c(2,1))
plot(price2$close ~ as.Date(price2$date),type="l",log="y",
xlab="Date",ylab="price (USD)",main="USD price")
grid()
plot(tsi[,1] ~ as.Date(price2$date),type="l",col="blue",
xlab="Date",ylab="TSI",main="TSI")
lines(as.Date(price2$date), tsi[,2] , col="red" )
grid()
par(mfrow=c(1,1))
Send a push notification.
if ( eth_signal != "NONE") {
MYNOTE <- paste(MYNOTE,"ETH TSI is flashing a ", eth_signal)
}
n <- as.numeric(unlist(strsplit(params[params$indicator == "stoch",2],",")))
n1 <- n[1]
n2 <- n[2]
n3 <- n[3]
sto <- stoch(HLC(price), nFastK=n1 , nFastD=n2 , nSlowD=n2 , bounded = TRUE, smooth=n3)
price2 <- cbind(price,sto)
price2$signal <- price2$fastK > price2$fastD
tail(price2) %>% kbl() %>% kable_paper("hover", full_width = F)
| date | high | low | close | fastK | fastD | slowD | signal | |
|---|---|---|---|---|---|---|---|---|
| 295 | 2022-06-29 | 1152.685 | 1092.099 | 1098.944 | 0.1863697 | 0.1823511 | 0.1757180 | TRUE |
| 296 | 2022-06-30 | 1103.690 | 1009.095 | 1067.299 | 0.1869669 | 0.1844323 | 0.1787056 | TRUE |
| 297 | 2022-07-01 | 1100.224 | 1040.151 | 1059.767 | 0.1930858 | 0.1868895 | 0.1817101 | TRUE |
| 298 | 2022-07-02 | 1073.053 | 1033.962 | 1066.513 | 0.2020019 | 0.1907580 | 0.1847309 | TRUE |
| 299 | 2022-07-03 | 1083.419 | 1044.006 | 1073.767 | 0.2093972 | 0.1955643 | 0.1879990 | TRUE |
| 300 | 2022-07-04 | 1106.814 | 1048.396 | 1094.569 | 0.2164346 | 0.2015773 | 0.1918443 | TRUE |
today_signal <- price2[nrow(price2),"signal"]
previous_signal <- price2[(nrow(price2)-1),"signal"]
if (today_signal != previous_signal ) {
if ( (today_signal == TRUE ) & (previous_signal == FALSE ) ) {
eth_signal="BUY"
}
if ( (today_signal == FALSE ) & (previous_signal == TRUE ) ) {
eth_signal="SELL"
}
} else {
eth_signal <- "NONE"
}
if (today_signal==TRUE) {
system("sed -i 's/ETH_STOCH_DAILY/BULLISH/' ../index1.html")
system("sed -i 's/ETH_STOCH_COL/green/' ../index1.html")
} else {
system("sed -i 's/ETH_STOCH_DAILY/BEARISH/' ../index1.html")
system("sed -i 's/ETH_STOCH_COL/red/' ../index1.html")
}
message(paste("ETH stoch signal:",eth_signal))
## ETH stoch signal: NONE
par(mfrow=c(2,1))
plot(price2$close ~ as.Date(price2$date),type="l",log="y",
xlab="Date",ylab="price (USD)",main="USD price")
grid()
plot(price2$fastK ~ as.Date(price2$date),type="l",col="blue",
xlab="Date",ylab="index",main="stochastic oscillator")
lines(as.Date(price2$date), price2$fastD , col="red" )
grid()
par(mfrow=c(1,1))
Send a push notification.
if ( eth_signal != "NONE") {
MYNOTE <- paste(MYNOTE,"ETH Stoch is flashing a ", eth_signal)
}
n <- as.numeric(unlist(strsplit(params[params$indicator == "RSI2",2],",")))
n1 <- n[1]
n2 <- n[2]
rsi1 <- RSI(price$close,n=n1,maType=EMA)
rsi2 <- RSI(price$close,n=n2,maType=EMA)
price2 <- cbind(price,rsi1,rsi2)
price2$signal <- price2$rsi1 > price2$rsi2
tail(price2) %>% kbl() %>% kable_paper("hover", full_width = F)
| date | high | low | close | rsi1 | rsi2 | signal | |
|---|---|---|---|---|---|---|---|
| 295 | 2022-06-29 | 1152.685 | 1092.099 | 1098.944 | 34.17488 | 34.36202 | FALSE |
| 296 | 2022-06-30 | 1103.690 | 1009.095 | 1067.299 | 33.61753 | 33.83464 | FALSE |
| 297 | 2022-07-01 | 1100.224 | 1040.151 | 1059.767 | 33.48284 | 33.70727 | FALSE |
| 298 | 2022-07-02 | 1073.053 | 1033.962 | 1066.513 | 33.72929 | 33.93769 | FALSE |
| 299 | 2022-07-03 | 1083.419 | 1044.006 | 1073.767 | 34.00182 | 34.19215 | FALSE |
| 300 | 2022-07-04 | 1106.814 | 1048.396 | 1094.569 | 34.79868 | 34.93562 | FALSE |
today_signal <- price2[nrow(price2),"signal"]
previous_signal <- price2[(nrow(price2)-1),"signal"]
if (today_signal != previous_signal ) {
if ( (today_signal == TRUE ) & (previous_signal == FALSE ) ) {
eth_signal="BUY"
}
if ( (today_signal == FALSE ) & (previous_signal == TRUE ) ) {
eth_signal="SELL"
}
} else {
eth_signal <- "NONE"
}
if (today_signal==TRUE) {
system("sed -i 's/ETH_RSI_DAILY/BULLISH/' ../index1.html")
system("sed -i 's/ETH_RSI_COL/green/' ../index1.html")
} else {
system("sed -i 's/ETH_RSI_DAILY/BEARISH/' ../index1.html")
system("sed -i 's/ETH_RSI_COL/red/' ../index1.html")
}
message(paste("ETH RSI signal:",eth_signal))
## ETH RSI signal: NONE
par(mfrow=c(2,1))
plot(price2$close ~ as.Date(price2$date),type="l",log="y",
xlab="Date",ylab="price (USD)",main="USD price")
grid()
plot(price2$rsi1 ~ as.Date(price2$date),type="l",col="blue",
xlab="Date",ylab="index",main="double RSI")
lines(as.Date(price2$date), price2$rsi2 , col="red" )
grid()
par(mfrow=c(1,1))
par(mfrow=c(2,1))
plot(price2$close ~ as.Date(price2$date),type="l",log="y",
xlab="Date",ylab="price (USD)",main="USD price")
grid()
plot(price2$rsi1/price2$rsi2 ~ as.Date(price2$date),type="l",
xlab="Date",ylab="RSI ratio",main="double RSI")
grid()
abline(h=1,lty=2,lwd=2)
par(mfrow=c(1,1))
Send a push notification.
if ( eth_signal != "NONE") {
MYNOTE <- paste(MYNOTE,"ETH RSI2 is flashing a", eth_signal)
}
#if ( !is.null(MYNOTE) ) {
# MYNOTE <- paste(MYNOTE,". Visit https://mdz-analytics.com/crypto/alerts.html for the details")
# pbPost("note", "Crypto Alert", MYNOTE, channel="eth_signal")
#}
For reproducibility
sessionInfo()
## R version 4.1.2 (2021-11-01)
## Platform: aarch64-unknown-linux-gnu (64-bit)
## Running under: Ubuntu 22.04 LTS
##
## Matrix products: default
## BLAS: /usr/lib/aarch64-linux-gnu/blas/libblas.so.3.10.0
## LAPACK: /usr/lib/aarch64-linux-gnu/lapack/liblapack.so.3.10.0
##
## locale:
## [1] LC_CTYPE=en_AU.UTF-8 LC_NUMERIC=C
## [3] LC_TIME=en_AU.UTF-8 LC_COLLATE=en_AU.UTF-8
## [5] LC_MONETARY=en_AU.UTF-8 LC_MESSAGES=en_AU.UTF-8
## [7] LC_PAPER=en_AU.UTF-8 LC_NAME=C
## [9] LC_ADDRESS=C LC_TELEPHONE=C
## [11] LC_MEASUREMENT=en_AU.UTF-8 LC_IDENTIFICATION=C
##
## attached base packages:
## [1] grid stats graphics grDevices utils datasets methods
## [8] base
##
## other attached packages:
## [1] kableExtra_1.3.4 RPushbullet_0.3.4 quantmod_0.4.20 TTR_0.24.3
## [5] xts_0.12.1 zoo_1.8-10 runner_0.4.1 forcats_0.5.1
## [9] stringr_1.4.0 dplyr_1.0.9 purrr_0.3.4 readr_2.1.2
## [13] tidyr_1.2.0 tibble_3.1.7 ggplot2_3.3.6 tidyverse_1.3.1
## [17] jsonlite_1.8.0 png_0.1-7
##
## loaded via a namespace (and not attached):
## [1] Rcpp_1.0.8.3 svglite_2.1.0 lattice_0.20-45 lubridate_1.8.0
## [5] assertthat_0.2.1 digest_0.6.29 utf8_1.2.2 R6_2.5.1
## [9] cellranger_1.1.0 backports_1.4.1 reprex_2.0.1 evaluate_0.15
## [13] httr_1.4.3 highr_0.9 pillar_1.7.0 rlang_1.0.2
## [17] curl_4.3.2 readxl_1.4.0 rstudioapi_0.13 jquerylib_0.1.4
## [21] rmarkdown_2.14 webshot_0.5.3 munsell_0.5.0 broom_0.8.0
## [25] compiler_4.1.2 modelr_0.1.8 xfun_0.30 systemfonts_1.0.4
## [29] pkgconfig_2.0.3 htmltools_0.5.2 tidyselect_1.1.2 viridisLite_0.4.0
## [33] fansi_1.0.3 crayon_1.5.1 tzdb_0.3.0 dbplyr_2.1.1
## [37] withr_2.5.0 gtable_0.3.0 lifecycle_1.0.1 DBI_1.1.2
## [41] magrittr_2.0.3 scales_1.2.0 cli_3.3.0 stringi_1.7.6
## [45] fs_1.5.2 xml2_1.3.3 bslib_0.3.1 ellipsis_0.3.2
## [49] generics_0.1.2 vctrs_0.4.1 tools_4.1.2 glue_1.6.2
## [53] hms_1.1.1 parallel_4.1.2 fastmap_1.1.0 yaml_2.3.5
## [57] colorspace_2.0-3 rvest_1.0.2 knitr_1.39 haven_2.5.0
## [61] sass_0.4.1